#include <cpu/exec.h>
#include "../local-include/decode.h"
#include "all-instr.h"
#include <stdlib.h>
void difftest_skip_dut(int nr_ref, int nr_dut);

static inline void set_width(DecodeExecState *s, int width)
{
  if (width != 0)
    s->width = width;
}

static inline def_EHelper(special)
{
  switch (s->isa.instr.r.func)
  {
  case (0b100000): // add
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rs) + reg_l(s->isa.instr.r.rt);
    print_asm_template3(add);
    break;
  case (0b100001): // addu
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rs) + reg_l(s->isa.instr.r.rt);
    print_asm_template3(addu);
    break;
  case (0b100010): // sub
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rs) - reg_l(s->isa.instr.r.rt);
    print_asm_template3(sub);
    break;
  case (0b100011): // subu
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rs) - reg_l(s->isa.instr.r.rt);
    print_asm_template3(subu);
    break;
  case (0b101010): // slt
    reg_l(s->isa.instr.r.rd) = (int)reg_l(s->isa.instr.r.rs) < (int)reg_l(s->isa.instr.r.rt);
    print_asm_template3(slt);
    break;
  case (0b101011): // sltu
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rs) < reg_l(s->isa.instr.r.rt);
    print_asm_template3(sltu);
    break;
  case (0b011010): // div
    cpu.lo = (((int)reg_l(s->isa.instr.r.rs)) / ((int)reg_l(s->isa.instr.r.rt)));
    cpu.hi = (((int)reg_l(s->isa.instr.r.rs)) % ((int)reg_l(s->isa.instr.r.rt)));
    print_asm_template3(div);
    break;
  case (0b011011): // divu
    cpu.lo = ((uint32_t)reg_l(s->isa.instr.r.rs) / (uint32_t)reg_l(s->isa.instr.r.rt));
    cpu.hi = ((uint32_t)reg_l(s->isa.instr.r.rs) % (uint32_t)reg_l(s->isa.instr.r.rt));
    print_asm_template3(divu);
    break;
  case (0b011000): // mult
    cpu.hi = ((long long)((int)reg_l(s->isa.instr.r.rs)) * (long long)((int)reg_l(s->isa.instr.r.rt))) >> 32;
    cpu.lo = ((long long)((int)reg_l(s->isa.instr.r.rs)) * (long long)((int)reg_l(s->isa.instr.r.rt))) % (1ll << 32);
    print_asm_template3(mult);
    break;
  case (0b011001): // multu
    cpu.hi = ((uint64_t)reg_l(s->isa.instr.r.rs) * (uint64_t)reg_l(s->isa.instr.r.rt)) >> 32;
    cpu.lo = ((uint64_t)reg_l(s->isa.instr.r.rs) * (uint64_t)reg_l(s->isa.instr.r.rt)) % (1ll << 32);
    print_asm_template3(multu);
    break;
  case (0b100100): // and
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rs) & reg_l(s->isa.instr.r.rt);
    print_asm_template3(and);
    break;
  case (0b100111): // nor
    reg_l(s->isa.instr.r.rd) = ~(reg_l(s->isa.instr.r.rs) | reg_l(s->isa.instr.r.rt));
    print_asm_template3(nor);
    break;
  case (0b100101): // or
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rs) | reg_l(s->isa.instr.r.rt);
    print_asm_template3(or);
    break;
  case (0b100110): // xor
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rs) ^ reg_l(s->isa.instr.r.rt);
    print_asm_template3(xor);
    break;
  case (0b000100): // sllv
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rt) << reg_l(s->isa.instr.r.rs);
    print_asm_template3(sllv);
    break;
  case (0b000000): // sll
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rt) << (s->isa.instr.r.sa);
    print_asm_template3(sll);
    break;
  case (0b000111): // srav
    reg_l(s->isa.instr.r.rd) = ((int)reg_l(s->isa.instr.r.rt) >> reg_l(s->isa.instr.r.rs));
    print_asm_template3(srav);
    break;
  case (0b000011): // sra
    reg_l(s->isa.instr.r.rd) = ((int)reg_l(s->isa.instr.r.rt) >> (s->isa.instr.r.sa));
    print_asm_template3(sra);
    break;
  case (0b000110): // srlv
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rt) >> reg_l(s->isa.instr.r.rs);
    print_asm_template3(srlv);
    break;
  case (0b000010): // srl
    reg_l(s->isa.instr.r.rd) = reg_l(s->isa.instr.r.rt) >> (s->isa.instr.r.sa);
    print_asm_template3(srl);
    break;
  case (0b001000): // jr
    cpu.is_cond = 1;
    cpu.cond_pc = reg_l(s->isa.instr.r.rs);
    print_asm_template1(jr);
    // difftest_skip_dut(2, 1);
    break;
  case (0b001001): // jalr
    cpu.is_cond = 1;
    cpu.cond_pc = reg_l(s->isa.instr.r.rs);
    reg_l(s->isa.instr.r.rd) = cpu.pc + 8;
    print_asm_template1(jalr);
    // difftest_skip_dut(2, 1);
    break;
  case (0b010000): // mfhi
    reg_l(s->isa.instr.r.rd) = cpu.hi;
    print_asm_template3(mfhi);
    break;
  case (0b010010): // mfho
    reg_l(s->isa.instr.r.rd) = cpu.lo;
    print_asm_template3(mfho);
    break;
  case (0b010001): // mthi
    cpu.hi = reg_l(s->isa.instr.r.rs);
    print_asm_template3(mthi);
    break;
  case (0b010011): // mtlo
    cpu.lo = reg_l(s->isa.instr.r.rs);
    print_asm_template3(mtlo);
    break;
  case (0b001101):
    printf("Congratulations, you pass the test!\n");
    exit(0);
    break;
  default:
    exec_inv(s);
  }
}

static inline void fetch_decode_exec(DecodeExecState *s)
{
  s->isa.instr.val = instr_fetch(&s->seq_pc, 4);
  switch (s->isa.instr.r.opcode)
  {
    EX(000, special)
    EX(010, addi)  // addi
    EX(011, addi)  // addiu
    EX(012, slti)  // slti
    EX(013, sltiu) // sltiu
    EX(014, andi)  // andi
    EX(015, ori)   // ori
    EX(016, xori)  // xori
    IDEX(017, IU, lui)
    EX(004, beq)          // beq
    EX(005, bne)          // bne
    EX(001, bgez)         // bgez,bltz,bgezal
    EX(007, bgtz)         // bgtz
    EX(006, blez)         // blez
    IDEX(002, j, j)       // j
    IDEX(003, j, jal)     // jal
    IDEXW(040, ld, lb, 1) // lb
    IDEXW(044, ld, ld, 1) // lbu
    IDEXW(041, ld, lb, 2) // lh
    IDEXW(045, ld, ld, 2) // lhu
    IDEXW(043, ld, ld, 4) // lw
    IDEXW(050, st, st, 1) // sb
    IDEXW(051, st, st, 2) // sh
    IDEXW(053, st, st, 4) // sw
    EX(074, nemu_trap)
  default:
    exec_inv(s);
  }
}

static inline void reset_zero()
{
  reg_l(0) = 0;
}

vaddr_t isa_exec_once()
{
  DecodeExecState s;
  s.is_jmp = 0;
  s.seq_pc = cpu.pc;

  fetch_decode_exec(&s);

  if (cpu.is_cond == 1)
  {
    cpu.is_cond = 2;
  }
  else if (cpu.is_cond == 2)
  {
    cpu.is_cond = 0;
    s.is_jmp = 1;
    s.jmp_pc = cpu.cond_pc;
  }
  update_pc(&s);

  reset_zero();

  return s.seq_pc;
}
